﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MySql.Data.MySqlClient;
using System.Data.Common;
using System.Globalization;

namespace DbSharp.Framework.Driver.FieldPersistence
{
    class MySqlFieldPersistence : IFieldPersistence
    {
        #region IFieldPersistence<MySqlDbType> Members

        public List<int> ListSupportedScalarDataType
        {
            get
            {
                var list = new List<int>();
                list.Add((int)MySqlDbType.Decimal);
                list.Add((int)MySqlDbType.Byte);
                list.Add((int)MySqlDbType.Int16);
                list.Add((int)MySqlDbType.Int32);
                list.Add((int)MySqlDbType.Float);
                list.Add((int)MySqlDbType.Double);
                list.Add((int)MySqlDbType.Timestamp);
                list.Add((int)MySqlDbType.Int64);
                list.Add((int)MySqlDbType.Int24);
                list.Add((int)MySqlDbType.Date);
                list.Add((int)MySqlDbType.Time);

                //MySQL_Datetime,  obsolete
                list.Add((int)MySqlDbType.DateTime);
                list.Add((int)MySqlDbType.Year);
                list.Add((int)MySqlDbType.VarString);
                list.Add((int)MySqlDbType.Bit);
                list.Add((int)MySqlDbType.NewDecimal);
                list.Add((int)MySqlDbType.Enum);
                list.Add((int)MySqlDbType.Set);
                list.Add((int)MySqlDbType.VarChar);
                list.Add((int)MySqlDbType.String);
                list.Add((int)MySqlDbType.UByte);
                list.Add((int)MySqlDbType.UInt16);
                list.Add((int)MySqlDbType.UInt32);
                list.Add((int)MySqlDbType.UInt64);
                list.Add((int)MySqlDbType.UInt24);
                list.Add((int)MySqlDbType.TinyText);
                list.Add((int)MySqlDbType.VarChar);

                return list;
            }
        }

        public List<int> ListSupportedRecordsetDataType
        {
            get
            {
                var list = new List<int>();
                return list;
            }
        }

        public string ConvertToString(object value, int valueType)
        {
            var valueType1 = (MySqlDbType)valueType;
            var exceptionFmt = "{0} type is not supported.";
            var exceptionMsg = string.Format(exceptionFmt, valueType1.ToString());
            if (!ListSupportedScalarDataType.Contains(valueType))
            {
                throw new NotSupportedException(exceptionMsg);
            }

            string result;
            switch (valueType1)
            {
                case MySqlDbType.Decimal:
                case MySqlDbType.Byte:
                case MySqlDbType.Int16:
                case MySqlDbType.Int32:
                case MySqlDbType.Float:
                case MySqlDbType.Double:
                case MySqlDbType.Timestamp:
                case MySqlDbType.Int64:
                case MySqlDbType.Int24:
                case MySqlDbType.Date:
                case MySqlDbType.Time:
                case MySqlDbType.DateTime:
                case MySqlDbType.Year:
                case MySqlDbType.VarString:
                case MySqlDbType.Bit:
                case MySqlDbType.NewDecimal:
                case MySqlDbType.Enum:
                case MySqlDbType.Set:
                case MySqlDbType.VarChar:
                case MySqlDbType.String:
                case MySqlDbType.UByte:
                case MySqlDbType.UInt16:
                case MySqlDbType.UInt32:
                case MySqlDbType.UInt64:
                case MySqlDbType.UInt24:
                case MySqlDbType.TinyText:
                    result = FieldPersistenceHelper.SafeConvertToString(value);
                    break;
                default:
                    throw new NotSupportedException(exceptionMsg);
            }


            return result;
        }

        public decimal RestoreStringToDecimal(string valueStr, int valueType)
        {
            var valueType1 = (MySqlDbType)valueType;
            decimal result;
            switch (valueType1)
            {
                case MySqlDbType.Decimal:
                case MySqlDbType.Byte:
                case MySqlDbType.Int16:
                case MySqlDbType.Int32:
                case MySqlDbType.Float:
                case MySqlDbType.Double:
                case MySqlDbType.Int64:
                case MySqlDbType.Int24:
                case MySqlDbType.Year:
                case MySqlDbType.Bit:
                case MySqlDbType.NewDecimal:
                case MySqlDbType.UByte:
                case MySqlDbType.UInt16:
                case MySqlDbType.UInt32:
                case MySqlDbType.UInt64:
                case MySqlDbType.UInt24:
                    result = Convert.ToDecimal(valueStr);
                    break;
                default:
                    throw new NotSupportedException(string.Format("{0} type is not supported.", valueType1.ToString()));
            }
            return result;
        }

        public Devart.Data.Oracle.OracleNumber RestoreStringToLargeDecimal(string valueStr, int valueType)
        {
            var valueType1 = (MySqlDbType)valueType;
            Devart.Data.Oracle.OracleNumber result;
            switch (valueType1)
            {
               
                case MySqlDbType.Decimal:
                case MySqlDbType.Byte:
                case MySqlDbType.Int16:
                case MySqlDbType.Int32:
                case MySqlDbType.Float:
                case MySqlDbType.Double:
                case MySqlDbType.Int64:
                case MySqlDbType.Int24:
                case MySqlDbType.Year:
                case MySqlDbType.Bit:
                case MySqlDbType.NewDecimal:
                case MySqlDbType.UByte:
                case MySqlDbType.UInt16:
                case MySqlDbType.UInt32:
                case MySqlDbType.UInt64:
                case MySqlDbType.UInt24:
                    result = Devart.Data.Oracle.OracleNumber.Parse(valueStr);
                    break;
                default:
                    throw new NotSupportedException(string.Format("{0} type is not supported.", valueType1.ToString()));
            }
            return result;
        }

        #region MySql.Data.Types.MetaData of MySql connector
        /*   
                public static bool MySql.Data.Types.MetaData.IsNumericType(string typename)
                {
                    switch (typename.ToLower(CultureInfo.InvariantCulture))
                    {
                        case "int":
                        case "integer":
                        case "numeric":
                        case "decimal":
                        case "dec":
                        case "fixed":
                        case "tinyint":
                        case "mediumint":
                        case "bigint":
                        case "real":
                        case "double":
                        case "float":
                        case "serial":
                        case "smallint":
                            return true;
                    }
                    return false;
               */

        private bool IsAtLeast(string serverVersion, int majorNum, int minorNum, int buildNum)
        {
            var versionParts = serverVersion.Split(new char[] { '.' });
            var thisMajor = int.Parse(versionParts[0]);
            var thisMinor = int.Parse(versionParts[1]);
            var thisBuild = int.Parse(versionParts[2]);
            return ((thisMajor > majorNum) || (((thisMajor == majorNum) && (thisMinor > minorNum)) || (((thisMajor == majorNum) && (thisMinor == minorNum)) && (thisBuild >= buildNum))));
        }

        //private MySqlDbType NameToType(string typeName, bool unsigned, bool realAsFloat, MySqlConnection connection)
        private MySqlDbType NameToType(string typeName, bool unsigned, bool realAsFloat)
        {
            switch (typeName.ToLower(CultureInfo.InvariantCulture))
            {
                case "char":
                    return MySqlDbType.String;

                case "varchar":
                    return MySqlDbType.VarChar;

                case "date":
                    return MySqlDbType.Date;

                case "datetime":
                    return MySqlDbType.DateTime;

                case "numeric":
                case "decimal":
                case "dec":
                case "fixed":

                    //if (!IsAtLeast(connection.ServerVersion, 5, 0, 3))
                    //{
                    //    return MySqlDbType.Decimal;
                    //}
                    return MySqlDbType.NewDecimal;

                case "year":
                    return MySqlDbType.Year;

                case "time":
                    return MySqlDbType.Time;

                case "timestamp":
                    return MySqlDbType.Timestamp;

                case "set":
                    return MySqlDbType.Set;

                case "enum":
                    return MySqlDbType.Enum;

                case "bit":
                    return MySqlDbType.Bit;

                case "tinyint":
                    if (unsigned)
                    {
                        return MySqlDbType.UByte;
                    }
                    return MySqlDbType.Byte;

                case "bool":
                case "boolean":
                    return MySqlDbType.Byte;

                case "smallint":
                    if (unsigned)
                    {
                        return MySqlDbType.UInt16;
                    }
                    return MySqlDbType.Int16;

                case "mediumint":
                    if (unsigned)
                    {
                        return MySqlDbType.UInt24;
                    }
                    return MySqlDbType.Int24;

                case "int":
                case "integer":
                    if (unsigned)
                    {
                        return MySqlDbType.UInt32;
                    }
                    return MySqlDbType.Int32;

                case "serial":
                    return MySqlDbType.UInt64;

                case "bigint":
                    if (unsigned)
                    {
                        return MySqlDbType.UInt64;
                    }
                    return MySqlDbType.Int64;

                case "float":
                    return MySqlDbType.Float;

                case "double":
                    return MySqlDbType.Double;

                case "real":
                    if (realAsFloat)
                    {
                        return MySqlDbType.Float;
                    }
                    return MySqlDbType.Double;

                case "text":
                    return MySqlDbType.Text;

                case "blob":
                    return MySqlDbType.Blob;

                case "longblob":
                    return MySqlDbType.LongBlob;

                case "longtext":
                    return MySqlDbType.LongText;

                case "mediumblob":
                    return MySqlDbType.MediumBlob;

                case "mediumtext":
                    return MySqlDbType.MediumText;

                case "tinyblob":
                    return MySqlDbType.TinyBlob;

                case "tinytext":
                    return MySqlDbType.TinyText;

                case "binary":
                    return MySqlDbType.Binary;

                case "varbinary":
                    return MySqlDbType.VarBinary;
            }
            throw new NotSupportedException("Unhandled type encountered");
        }

        #endregion 

        public int GetSpecificDbType(DbDataReader dataReader, int columnIndex)
        {
            //class: MySql.Data.MySqlClient.MySqlDataReader
            //method: public override string GetDataTypeName(int i)

            var reader = (MySql.Data.MySqlClient.MySqlDataReader)dataReader;
            var typeName = reader.GetDataTypeName(columnIndex);
            var mySqlDbType = NameToType(typeName, false, false);
            return (int)mySqlDbType;
        }
 

        public int GetSpecificDbType(DbParameter dbParam)
        {
            var typ = ((MySqlParameter)dbParam).MySqlDbType;
            return (int)typ;
        }


        public int NumberTypeWhenParseLiteral
        {
            get { return (int)(MySqlDbType.NewDecimal); }
        }

 

        public bool IsStringType(int valueType)
        {
            bool result = false;
            var valueType1 = (MySqlDbType)valueType;
            switch (valueType1)
            {   
                case MySqlDbType.VarString:
                case MySqlDbType.VarChar:
                case MySqlDbType.String: 
                case MySqlDbType.TinyText:
                    result = true;
                    break;
                default:
                    result = false;
                    break;
            }

            return result;
        }


        #endregion
    }
}